EventBridge Schedulerを使って、特定の期間のみNATゲートウェイを有効化することを実現する
こんちには。
データアナリティクス事業本部 機械学習チームの中村(nokomoro3)です。
本記事ではEventBridge Schedulerを使って、特定の期間のみNATゲートウェイを有効化することを実現したいと思います。
NATゲートウェイを有効化・無効化については以下を参考とし、EventBridge Schedulerではこの対象のテンプレートのUpdateStackを実行することで実現していきます。
作成するテンプレート
3つのテンプレートを作成してS3バケットに配置します。
今回はs3://cm-nakamura-sample-20240413/cloudformation/
にテンプレートを配置したとして進めます。
target.yml
は今回のEventBridge Schedulerでupdate-stackの対象となるVPCやSubnet、NATゲートウェイなどのリソースを定義します。
scheduler.yml
はEventBridge Schedulerの定義で、NATゲートウェイの起動と削除それぞれをSchedulerとして定義しています。
instance.yml
は動作確認用のEC2やSession Managerで接続するためのVPCエンドポイントの設定を記載しています。
target.ymlの定義
target.yml
ではEventBridge Schedulerでupdate-stackの対象となるVPCやSubnet、NATゲートウェイなどのリソースを定義します。
以下の元記事を参考に、PublicとPrivateの2個のSubnetだけのシンプルな構成にしました。
元記事と同様ですが、Parameterの"EnableNatGateway"
の部分がtrueであればNATゲートウェイが追加され、falseだと削除されるような形となります。
また、NATゲートウェイの作成に伴うEIPやRouteの追加もポイントになります。
AWSTemplateFormatVersion: "2010-09-09" Description: Network Layer Template #------------------------------------------------------------------------------ # Parameters #------------------------------------------------------------------------------ Parameters: SystemName: Description: This value is used as the resource prefix. Type: String MinLength: 1 Default: example Env: Description: Environment Name Type: String Default: dev AllowedValues: - dev - prd VpcCidr: Description: First and Second Octet of VPC, For example (10.0/172.16/192.168) Type: String Default: 10.0 AllowedPattern: "^(10\\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|172\\.(1[6-9]|2[0-9]|3[0-1])|192\\.168)$" ConstraintDescription: xxx.xxx EnableNatGateway: Description: Enable NAT Gateway. Type: String Default: false AllowedValues: [true, false] #------------------------------------------------------------------------------ # Conditions #------------------------------------------------------------------------------ Conditions: EnableNatGateway: !Equals [true, !Ref EnableNatGateway] #------------------------------------------------------------------------------ # Mappings #------------------------------------------------------------------------------ Mappings: VpcConfig: dev: Vpc : .0.0/16 PublicSubnet : .0.0/24 PrivateSubnet: .10.0/24 #------------------------------------------------------------------------------ # Resources #------------------------------------------------------------------------------ Resources: Vpc: Type: AWS::EC2::VPC Properties: CidrBlock: !Sub [ "${VpcCidr}${Subnet}", { Subnet: !FindInMap [ VpcConfig, !Ref Env, Vpc ]}] EnableDnsSupport: true EnableDnsHostnames: true InstanceTenancy: default Tags: - Key: Name Value: !Sub ${SystemName}-${Env}-vpc InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${SystemName}-${Env}-igw AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref Vpc InternetGatewayId: !Ref InternetGateway PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub ${SystemName}-${Env}-public-rtb PublicRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub ${SystemName}-${Env}-private-rtb PrivateRoute: Type: AWS::EC2::Route Condition: EnableNatGateway Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway PublicSubnet: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select [ 0, "Fn::GetAZs": { Ref: "AWS::Region" } ] CidrBlock: !Sub [ "${VpcCidr}${Subnet}", { Subnet: !FindInMap [ VpcConfig, !Ref Env, PublicSubnet ]}] MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${SystemName}-${Env}-public-subnet VpcId: !Ref Vpc PublicSubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet RouteTableId: !Ref PublicRouteTable PrivateSubnet: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select [ 0, "Fn::GetAZs": { Ref: "AWS::Region" } ] CidrBlock: !Sub [ "${VpcCidr}${Subnet}", { Subnet: !FindInMap [ VpcConfig, !Ref Env, PrivateSubnet ]}] Tags: - Key: Name Value: !Sub ${SystemName}-${Env}-private-subnet VpcId: !Ref Vpc PrivateRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet RouteTableId: !Ref PrivateRouteTable NatGateway: Type: AWS::EC2::NatGateway Condition: EnableNatGateway Properties: AllocationId: !GetAtt NatGatewayEIP.AllocationId SubnetId: !Ref PublicSubnet Tags: - Key: Name Value: !Sub ${SystemName}-${Env}-ngw NatGatewayEIP: Type: AWS::EC2::EIP Condition: EnableNatGateway Properties: Domain: vpc Outputs: Vpc: Value: !Ref Vpc Export: Name: !Sub ${SystemName}-${Env}-vpc PublicSubnet: Value: !Ref PublicSubnet Export: Name: !Sub ${SystemName}-${Env}-public-subnet PrivateSubnet: Value: !Ref PrivateSubnet Export: Name: !Sub ${SystemName}-${Env}-private-subnet
instance.ymlの定義
instance.ymlは動作確認用のEC2やSession Managerで接続するためのVPCエンドポイントの設定を記載しています。EC2はPrivateSubnetに配置します。
こちらのテンプレートは以下の記事を参考にいたしました。
なおAMIはal2023-ami-minimal-*
を使用しないようご注意ください。minimalだとSession Managerによる接続ができなくなるようです。
AWSTemplateFormatVersion: "2010-09-09" Description: AWS Private Subnet EC2 Launch and Connect by SSM #------------------------------------------------------------------------------ # Parameters #------------------------------------------------------------------------------ Parameters: InstanceType: Type: String Default: "t2.nano" VPCId: Type: String SubnetId: Type: String PJPrefix: Type: String Default: "cm-nakamura-ec2" LatestAmiId: Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>' Default: '/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64' #------------------------------------------------------------------------------ # Resources #------------------------------------------------------------------------------ Resources: EC2SG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: "Bastion EC2 Security Group" VpcId: !Ref VPCId Tags: - Key: Name Value: !Sub "${PJPrefix}-ec2-bastion-sg" EndpointSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: "Bastion Endpoint Security Group" VpcId: !Ref VPCId SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 SourceSecurityGroupId: !Ref EC2SG Tags: - Key: Name Value: !Sub "${PJPrefix}-endpoint-bastion-sg" IamRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "${PJPrefix}-ec2-bastion-role" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - !Ref IamRole EC2Instance: Type: "AWS::EC2::Instance" Properties: Tags: - Key: Name Value: !Sub "${PJPrefix}-bastion" ImageId: !Ref LatestAmiId InstanceType: !Ref InstanceType SecurityGroupIds: - !Ref EC2SG SubnetId: !Ref SubnetId IamInstanceProfile: !Ref InstanceProfile SSMVPCEndpoint: Type: "AWS::EC2::VPCEndpoint" Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssm' VpcId: !Ref VPCId SubnetIds: - !Ref SubnetId SecurityGroupIds: - !Ref EndpointSG SSMMessagesVPCEndpoint: Type: "AWS::EC2::VPCEndpoint" Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssmmessages' VpcId: !Ref VPCId SubnetIds: - !Ref SubnetId SecurityGroupIds: - !Ref EndpointSG EC2MessagesVPCEndpoint: Type: "AWS::EC2::VPCEndpoint" Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ec2messages' VpcId: !Ref VPCId SubnetIds: - !Ref SubnetId SecurityGroupIds: - !Ref EndpointSG
まずは手動で動作確認
EventBridge Schedulerで動作させる前に、まずはAWS CLIから動作確認していきます。
最初にtarget.yml
のcreate-stackを実行します。
aws --profile $PROFILE --region us-east-1 \ cloudformation create-stack \ --stack-name cm-nakamura-stack \ --template-url https://cm-nakamura-sample-20240413.s3.amazonaws.com/cloudformation/base.yml \ --parameters ParameterKey=SystemName,ParameterValue=cm-nakamura-sample
デフォルトではNATゲートウェイは作成されていません。
次に検証用のEC2インスタンスを作成します。
VPC_ID=$(aws --profile $PROFILE --region us-east-1 \ cloudformation describe-stacks \ --stack-name cm-nakamura-stack \ | jq -r '.Stacks[] | .Outputs[] | select(.OutputKey == "Vpc") | .OutputValue') PRIVATE_SUBNET_ID=$(aws --profile $PROFILE --region us-east-1 \ cloudformation describe-stacks \ --stack-name cm-nakamura-stack \ | jq -r '.Stacks[] | .Outputs[] | select(.OutputKey == "PrivateSubnet") | .OutputValue') aws --profile $PROFILE --region us-east-1 \ cloudformation create-stack \ --stack-name cm-nakamura-instance-stack \ --template-url https://cm-nakamura-sample-20240413.s3.amazonaws.com/cloudformation/instance.yml \ --capabilities CAPABILITY_NAMED_IAM \ --parameters "ParameterKey=VPCId,ParameterValue=${VPC_ID}" "ParameterKey=SubnetId,ParameterValue=${PRIVATE_SUBNET_ID}"
起動がおわりましたら、マネジメントコンソールからSession Managerを使ってEC2インスタンスに接続してpingしてみましょう。
想定通り失敗します。
ping www.google.com -c 5 # PING www.google.com (172.253.63.147) 56(84) bytes of data. # # --- www.google.com ping statistics --- # 5 packets transmitted, 0 received, 100% packet loss, time 4130ms
次に、update-stackでNATゲートウェイを作成します。
前述の通りParameterKey=EnableNatGateway,ParameterValue=true
の部分がポイントです。
aws --profile $PROFILE --region us-east-1 \ cloudformation update-stack \ --stack-name cm-nakamura-stack \ --use-previous-template \ --parameters ParameterKey=SystemName,ParameterValue=cm-nakamura-sample ParameterKey=EnableNatGateway,ParameterValue=true
再度、マネジメントコンソールからEC2インスタンスに接続してpingしてみましょう。
次は想定通り成功するようになっています。
ping www.google.com -c 5 # PING www.google.com (142.251.16.147) 56(84) bytes of data. # 64 bytes from bl-in-f147.1e100.net (142.251.16.147): icmp_seq=1 ttl=57 time=3.61 ms # 64 bytes from bl-in-f147.1e100.net (142.251.16.147): icmp_seq=2 ttl=57 time=2.56 ms # 64 bytes from bl-in-f147.1e100.net (142.251.16.147): icmp_seq=3 ttl=57 time=2.56 ms # 64 bytes from bl-in-f147.1e100.net (142.251.16.147): icmp_seq=4 ttl=57 time=2.54 ms # 64 bytes from bl-in-f147.1e100.net (142.251.16.147): icmp_seq=5 ttl=57 time=2.54 ms # --- www.google.com ping statistics --- # 5 packets transmitted, 5 received, 0% packet loss, time 4005ms # rtt min/avg/max/mdev = 2.540/2.764/3.613/0.424 ms
再び、update-stackでNATゲートウェイを削除しておきます。
aws --profile $PROFILE --region us-east-1 \ cloudformation update-stack \ --stack-name cm-nakamura-stack \ --use-previous-template \ --parameters ParameterKey=SystemName,ParameterValue=cm-nakamura-sample ParameterKey=EnableNatGateway,ParameterValue=false
scheduler.ymlの定義
EventBridge Schedulerのテンプレートを見ていきます。
こちらは先ほど手動で行ったNATゲートウェイの起動と削除それぞれSchedulerとして定義しています。
こちらは実際にはマネジメントコンソールから作成・テストして、IaCジェネレータを用いて出力したものを整形しました。
IaCジェネレータについては以下を参照ください。
AWSTemplateFormatVersion: "2010-09-09" Description: NatGateway up down Schedulers Parameters: TargetStackName: Description: This value is used as the resource prefix. Type: String MinLength: 1 Default: example-stack Resources: IAMRole: Type: AWS::IAM::Role Properties: RoleName: "eventbridge-execution-role" Description: "Role for EventBridge Scheduler" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: "sts:AssumeRole" Principal: Service: - "scheduler.amazonaws.com" ManagedPolicyArns: - "arn:aws:iam::aws:policy/PowerUserAccess" SchedulerUp: Type: "AWS::Scheduler::Schedule" Properties: Name: "cm-nakamura-up-natgw" GroupName: "default" ScheduleExpression: "cron(00 10 13 * ? *)" ScheduleExpressionTimezone: "Asia/Tokyo" FlexibleTimeWindow: Mode: "OFF" State: "ENABLED" Target: Arn: "arn:aws:scheduler:::aws-sdk:cloudformation:updateStack" Input: !Sub |- { "StackName": "${TargetStackName}", "UsePreviousTemplate": "true", "Parameters": [ { "ParameterKey": "SystemName", "ParameterValue": "cm-nakamura-sample" }, { "ParameterKey": "EnableNatGateway", "ParameterValue": "true" } ] } RoleArn: Fn::GetAtt: - "IAMRole" - "Arn" SchedulerDown: Type: "AWS::Scheduler::Schedule" Properties: Name: "cm-nakamura-down-natgw" GroupName: "default" ScheduleExpression: "cron(30 10 13 * ? *)" ScheduleExpressionTimezone: "Asia/Tokyo" FlexibleTimeWindow: Mode: "OFF" State: "ENABLED" Target: Arn: "arn:aws:scheduler:::aws-sdk:cloudformation:updateStack" Input: !Sub |- { "StackName": "${TargetStackName}", "UsePreviousTemplate": "true", "Parameters": [ { "ParameterKey": "SystemName", "ParameterValue": "cm-nakamura-sample" }, { "ParameterKey": "EnableNatGateway", "ParameterValue": "false" } ] } RoleArn: Fn::GetAtt: - "IAMRole" - "Arn"
作成するリソースはScheduler向けのIAMロールと、Schedulerの2つとなります。
2つのSchedulerはそれぞれ対象となるスタックを更新しますが、対象スタックに対するパラメータの"EnableNatGateway"
の部分がtrue, falseと異なる形となります。
前述の通りtrueだとNATゲートウェイが追加され、falseだとNATゲートウェイが削除されます。
スケジューラの実行時間はcronで設定しており、毎月13日の10時に起動され、毎月13日の10時30分に停止されるような設定内容となります。ここは必要に応じて修正してお使いください。
動作確認
まずはscheduler.yml
のcreate-stackを行います。
aws --profile $PROFILE --region us-east-1 \ cloudformation create-stack \ --stack-name cm-nakamura-scheduler-stack \ --template-url https://cm-nakamura-sample-20240413.s3.amazonaws.com/cloudformation/scheduler.yml \ --capabilities CAPABILITY_NAMED_IAM \ --parameters ParameterKey=TargetStackName,ParameterValue=cm-nakamura-stack
時間まで待ち、まずは想定通りに13日の10時にNATゲートウェイとその他のリソースが作成されることを確認できました。
EC2からの動作確認は必要に応じて行われてください。(ここでは割愛いたします)
次に13日の10時30分にNATゲートウェイとその他のリソースが削除されることを確認できました。
後片付け
作成したスタックを一通り削除しておきましょう。
aws --profile $PROFILE --region us-east-1 \ cloudformation delete-stack \ --stack-name cm-nakamura-scheduler-stack aws --profile $PROFILE --region us-east-1 \ cloudformation delete-stack \ --stack-name cm-nakamura-instance-stack aws --profile $PROFILE --region us-east-1 \ cloudformation delete-stack \ --stack-name cm-nakamura-stack
まとめ
いかがでしたでしょうか。本記事がCloudFormationとEventBridge Schedulerを組み合わせて活用される方のご参考になれば幸いです。
参考
- 時限式CloudFormation を作成してみた | DevelopersIO
- 時限式CloudFormation を作成してみた その2 | DevelopersIO
- ECSとStep Functionsでdbtを動かし、Redshiftへデータを連携するデータパイプラインを構築する | DevelopersIO
- 必要な時だけNAT Gatewayを作成する方法 | DevelopersIO
- EventBridgeからEventBridge Schedulerに移行してみる #Python - Qiita
- AWS CloudFormation IaC generatorを試してみた。 | DevelopersIO
- スタックの作成 - AWS CloudFormation
- cron 式のリファレンス - Amazon EventBridge
- EventBridge Schedulerを使ってEC2の起動停止を行う(できればCFnまで)
- 【CloudFormation】一撃でプライベートサブネットにEC2を起動し、SSMポートフォワード経由でRDPする | DevelopersIO
- AWS CloudFormation IaC generatorを試してみた。 | DevelopersIO